<?php

namespace Xin\HttpClient;


trait HasHttpClient
{
	/**
	 * @var HttpClient
	 */
	protected $httpClient;

	/**
	 * @var \Closure
	 */
	private $requestInterceptor;

	/**
	 * @var \Closure
	 */
	private $responseInterceptor;

	/**
	 * @var array
	 */
	protected static $httpClientDefaults = [
		'curl' => [
			CURLOPT_IPRESOLVE => CURL_IPRESOLVE_V4,
		],
	];

	/**
	 * 设置 guzzle 全局默认设置。
	 *
	 * @param array $defaults
	 */
	public static function setHttpClientDefaultOptions($defaults = [])
	{
		static::$httpClientDefaults = $defaults;
	}

	/**
	 * 返回当前的 guzzle 全局默认设置。
	 */
	public static function getHttpClientDefaultOptions()
	{
		return static::$httpClientDefaults;
	}

	/**
	 * Set HttpClient.
	 *
	 * @return $this
	 */
	public function setHttpClient(HttpClient $httpClient)
	{
		$this->httpClient = clone $httpClient;
		$this->httpClient->setOptions(
			$this->httpClient->getOptions() + $this->getHttpClientOptions()
		);

		$this->setupHttpClient($httpClient);

		return $this;
	}

	/**
	 * 获取 HttpClient 选项。
	 * @return array
	 */
	public function getHttpClientOptions()
	{
		$options = static::getHttpClientDefaultOptions();

		// 尝试获取 AccessToken
		$accessToken = $this->getAccessToken();
		if ($accessToken) {
			$options['headers'] = [
				'Authorization' => 'Bearer ' . $accessToken,
			];
		}

		// 获取实例默认配置
		$options = array_replace_recursive($options, $this->httpClientOptions());

		// 获取自定义配置
		$customOptions = [];
		if (method_exists($this, 'getConfig')) {
			$customOptions = (array)$this->getConfig('http') ?: (array)$this->getConfig('http_client');
		}

		return array_replace_recursive(
			$options,
			$customOptions
		);
	}

	/**
	 * HttpClient 选项。
	 * @return array
	 */
	protected function httpClientOptions()
	{
		return [];
	}

	/**
	 * 获取 AccessToken
	 * @return string|null
	 */
	protected function getAccessToken()
	{
		return null;
	}

	/**
	 * 返回 Http 客户端实例
	 * @return HttpClient
	 */
	public function client()
	{
		if (!$this->httpClient) {
			$this->httpClient = HttpClient::create($this->getHttpClientOptions());
			$this->setupHttpClient($this->httpClient);
		}

		return $this->httpClient;
	}

	/**
	 * 设置 HttpClient
	 * @param HttpClient $httpClient
	 */
	protected function setupHttpClient(HttpClient $httpClient)
	{
		if (!$this->requestInterceptor) {
			$this->requestInterceptor = \Closure::fromCallable([$this, 'onHttpClientRequest']);
		}
		$httpClient->removeRequestInterceptor($this->requestInterceptor);
		$httpClient->addRequestInterceptor($this->requestInterceptor);

		if (!$this->responseInterceptor) {
			$this->responseInterceptor = \Closure::fromCallable([$this, 'onHttpClientResponse']);
		}
		$httpClient->removeResponseInterceptor($this->responseInterceptor);
		$httpClient->addResponseInterceptor($this->responseInterceptor);
	}

	/**
	 * 请求拦截器
	 * @param array $options
	 * @return array
	 */
	protected function onHttpClientRequest(array $options)
	{
		return $options;
	}

	/**
	 * 响应拦截器
	 * @param Response $response
	 * @return Response
	 */
	protected function onHttpClientResponse(Response $response)
	{
		return $response;
	}

	/**
	 * Http Get
	 * @param string $uri
	 * @param array $query
	 * @param array $options
	 * @return Response|mixed|array|null
	 */
	public function httpGet(string $uri, array $query = [], array $options = [])
	{
		return $this->client()->get($uri, $query, $options);
	}

	/**
	 * Http Post
	 * @param string $uri
	 * @param array $data
	 * @param array $options
	 * @return Response|mixed|array|null
	 */
	public function httpPost(string $uri, array $data = [], array $options = [])
	{
		$data = $data ?: new \stdClass();
		return $this->client()->post($uri, $data, $options);
	}

	/**
	 * Http Put
	 * @param string $uri
	 * @param array $data
	 * @param array $options
	 * @return Response|mixed|array|null
	 */
	public function httpPut(string $uri, array $data = [], array $options = [])
	{
		$data = $data ?: new \stdClass();
		return $this->client()->put($uri, $data, $options);
	}

	/**
	 * Http Delete
	 * @param string $uri
	 * @param array $data
	 * @param array $options
	 * @return Response|mixed|array|null
	 */
	public function httpDelete(string $uri, array $data = [], array $options = [])
	{
		$data = $data ?: new \stdClass();
		return $this->client()->delete($uri, $data, $options);
	}
}
